导航
导航
Posts List
  1. 概要
  2. 内容
  3. 介绍
  4. 系统架构
  5. 子系统架构
  6. 总结

【译】Linux内核架构概要

概要

这篇文章描述的是Linux内核概念上的软件架构。这里说的架构是和内核中高伸缩性的子系统相关,和特殊的程序或变量无关。这样一个架构概要的目的之一是为了对Linux的开发人员和架构师能够在脑中形成一个模型。这个模型可能并不能很好的反应实际的架构,但它能提供一个有效的方式来思考整体的架构。这个模型对于大部分入门水平的开发人员来说是非常有帮助的,但同时对于有经验的开发者来说也是保持连续和准确的系统词汇的有效方式。

这架构的出现是对现有的Linux实现的结果;使用的主要信息源是文档和源码。不幸的是,并没有对Linux的开发人员的采访来提取系统的架构。

Linux内核有五个主要的子系统组成,通过程序调用(procedure call)来进行通信。在五个子系统中,四个子系统将从模块互联的层面来进行讨论。始终将考虑到特殊子系统和linux系统全局的关系。

这样的Linux内核架构是Linux成功被许多使用者采用的原因之一。特别地,Linux内核架构被设计成支持大量地志愿开发人员进行开发。而且最可能需要进行功能增加的子系统被构造成了容易扩展。这是整个系统成功的两个主要因素。

内容
  1. 介绍

    1.1 目的

    1.2 文章的挑战

    1.3 组织结构

  2. 系统架构

    2.1 系统概览

    2.2 内核用途

    2.3 内核架构概览

    2.4 多开发支持

    2.5 系统数据结构

  3. 子系统架构

    3.1 进程调度器架构 — Process Scheduler

    3.2 内存管理器架构 — Memory Manager

    3.3 虚拟文件系统架构 — Virtual FIle System

    3.4 网络接口架构 — Network Interface

    3.5 进程间通信架构 — Inter-Process Communication

  4. 结论


介绍
  • 目的

这篇文章的目的是提出Linux内核架构的概要。这被Soni描述为了conceptual architecture。通过提取高层次的设计,这个架构对于入门的开发者是非常有帮助的,因为在理解程序作用的位置之前需要了解高层次的架构,而架构概要则是形成术语的有效方式[system vocabulary],而这些术语通常在有经验的开发者和系统设计者中共同使用。这篇架构性的描述可能并不能很全面的反应真实实现的架构,但能在思想上提供有用的模型来分享。理想的情况下,架构的概要应该在系统实现前就完成,并随着系统的持续性进行更新。

  • 文章的挑战

这个发表在某种意义上是不正常的,在与架构概要通常是在系统完工时就应该形成。因为文章的作者并没有参与Linux系统的设计与实现,这篇文章的是对2.0.27内核源码进行逆向工程和文档的结果。用到了少量的架构性的描述,但这些描述也是基于已存在的系统实现。通过已存在的系统实现推到出架构概要,所以文章中出现一些实现细节来作为架构概要。

除此之外,文章中抽取信息的机制忽略了最好的信息来源 — 系统的架构师和开发者才是活生生的知识。为了系统架构的合理概要,应该要对这些个体进行采访。只有以这种方式才能描述准确的系统架构模型。

  • 组织结构

接下来的部分完整的描述整体的目标和Linux内核架构。接着,每个独立的子系统以模块进行阐述,并对子系统中的模块间的关系进行讨论。最后将讨论Linux内核架构对系统的实现和系统的整体成功是如何发挥作用的。


系统架构
  • 系统概览

孤立的Linux内核是无法起作用的;只有在一个更大的系统中,作为一个整体中的一部分才能发挥作用。因此,在整个系统上下文中讨论内核才是有意义的。图2.1展示了整个Linux操作系统的分解。 Linux分解子系统

Linux操作系统由四个主要的子系统组成:

  1. 用户应用程序 User Applications – 系统使用的用户应用集通常根据使用的电脑系统的不同而不同,但通常情况下包括word处理应用和浏览器。
  2. O/S服务 O/S Services – 系统服务通常被当作操作系统的一部分(比如windowing system,command shell),还有对内核的编程接口也包括在这个子系统中。
  3. Linux内核 Linux Kernel – 是本篇文章主要的关注点; 内核抽象和调控对硬件资源的访问,包括CPU
  4. 硬件控制器 Hardware Controllers – 这个子系统由linux安装中所有可能的物理驱动组成;比如,CPU,内存,硬盘,网络硬件等等。

这个分解遵循了分层架构;每个子系统层只和直接相邻的子系统通信。此外,子系统间的依赖关系是从上到下:图上层的子系统依赖下层的子系统,但下层的子系统不依赖上层的子系统。

因为文章的主要关注点在Linux内核,这里将会完全的忽略用户应用程序子系统,只考虑硬件和O/S 服务子系统和Linux内核子系统的交界。

  • 内核用途

Linux内核向用户进程提供了虚拟机器接口。开发者可以在不知道电脑上安装的物理硬件是什么的情况下编写程序 – Linux内核将所有的硬件抽象成了一致的虚拟接口。此外,Linux在某种意义上支持对用户进程透明的多任务:每个进程进程就好像在独自在电脑上执行,独占主内存和其他硬件资源。内核实际上并行地运行多个进程,并负责协调硬件资源的访问,使每个进程可以公平的进行访问的同时维护进程间的安全。

  • 内核架构概览

Linux内核由五个主要的子系统组成:

  1. 进程调度器Process Scheduler,负责进程对CPU的访问。调度器推行确保每个进程可以公平的访问CPU的策略,同时保证内核及时执行必要的硬件动作。
  2. 内存管理器Memory Manager,使多个进程可以安全的分享机器的主内存系统。此外,内存管理器支持虚拟内存使得Linux可以支持进程使用比系统可用内存更多的内存。
  3. 虚拟文件系统Virtual File System,通过对所有的设备提出一个统一文件接口来抽象不同的硬件设备的细节。此外,虚拟文件系统支持多文件系统格式来对其他操作系统进行兼容。
  4. 网络接口Network Interface,提供对不同的网络标准协议和不同的网络硬件设备进行访问。
  5. 进程间通信Inter-Process Communication,通过多种机制来支持在单一的Linux系统中进程间通信。

图2.2展示了对Linux内核高层次的分解,图中的线从从属的子系统画向依赖的子系统。

Linux子系统概览

图中突出了最中心的子系统是进程调度器:所有的其他子系统都要依赖进程调度器,这是因为所有的子系统需要挂起和恢复进程。通常一个子系统需要等待一个硬件操作完成时会挂起一个进程,当操作完成时会恢复进程。比如,当一个进程试图通过网络发送一条消息,在消息发送后(或者消息发送失败),网络接口在返回状态码表示操作成功或失败后会恢复进程。其他子系统都因为类似的原因而需要依赖进程调度器。

其他依赖在某种程度上是比较不明显的,但相当重要:

  • 进程调度器使用内存管理器来对进程恢复时的内存映射进行调整。
  • 进程间通信子系统依赖内存管理器来支持共享内存通信机制。这个机制允许两个进程访问除各自私有内存之外的共有内存。
  • 虚拟文件系统使用网络接口来支持网络文件系统network file system,同时使用内存管理器来提供内存虚拟硬盘ramdisk设备。
  • 内存管理器使用虚拟文件系统支持swapping;这是内存管理器依赖依赖进程调度器的唯一理由。当一个进程访问被交换出去的内存时,内存管理器向文件系统执行从持久化存储中读取内存操作的请求,并挂起进程。

除了图中明确表示出来的依赖,内核中所有子系统需要依赖一些没有表示出来的共同资源,包括所有内核子系统使用来进程分配和释放内核使用的内存的程序,打印警告和错误信息的程序,以及系统调试程序。这些并不会被明确的提及因为这些程序被假定无处不在,并在图2.1中的Kernel层使用。

每个描述的子系统包含的状态信息可以通过程序接口进行获取,并且每个子系统负责维护所管理资源的完整性。

  • 支持多开发者

Linux系统由众多的志愿者开发。大量的开发者以及开发者的自愿性质对如何架构系统有很大的影响。随着大量地理上隔离的开发者,高耦合度的系统进行开发是十分困难的 – 开发者会不断的设计其他人的代码。因为这个原因,Linux系统设计的子系统需要对大部分的修改要有预见 – 文件系统,硬件接口,网络系统 – 被设计成高度模块画。举个例子,一个Linux系统的实现是希望可以支持许多硬件的,这些硬件都有着不同的接口。一个没有经验的架构会在一个子系统中实现所有的硬件设备。一个对多开发者更好的方法是,将每个硬件设备的代码分离到设备驱动中device dirver,设备驱动是在文件系统中的独立的模块。

图2.3是开发者的档案文件:

Linux开发者档案

图2.3展示了参与了Linux内核开发的大部分开发者,以及已经实现的区域。少部分开发者参与修改内核的多个部分。为了清晰起见,图中没有包含这些开发人员。比如,Linus Torvalds是Linux内核子系统的原始实现者,不过随后的开发有其他开发者完成。这个图并没有考虑准确因为在开发内核期间开发者的署名并没有很好的维护,但也给出了开发人员努力的领域。

这图证实了早前表述内核的高弹性架构。非常有趣的去注意到,少量开发者会涉及多个子系统,主要发生在存在子系统依赖的地方。系统结构通常反应了开发结构。大部分的开发者的工作集中在硬件设备驱动,逻辑文件模块,网络设备驱动,网络协议模块。所以这四个模块是kernel中设计最为可扩展的部分。

  • 系统数据结构
  1. Task List:进程调度器为每个活动的进程维护一个数据块。这些数据块被存储在被称为task list的链表中;进程调度器通常维护一个current的指针表示当前活动的进程。
  2. Memory Map: 内存管理器存储着每个进程虚拟地址与物理地址的映射。同时也存储着如何取回和替换特定的页所需要的额外信息。这个信息被存储在这个进程的task list中的内存映射数据结构中。
  3. I-nodes:虚拟文件系统使用index-node(i-nodes)来代表逻辑文件系统中的文件。i-node数据结构存储着文件块的数量和物理设备地址的映射关系。如果两个进程同时打开同一个文件时,可以跨进程共享I-node数据结构,通过两个task数据块指向同一个i-node来进行共享。
  4. Data Connection:所有的数据结构的根源都在进程调度器的task list中。系统中的每一个进程都有一个数据结构(task data structure),包括着指向它的内存映射信息的指针,指向所有打开文件的i-nodes的指针,指向代表当前进程和其他task有联系的开放网络连接的数据结构。

子系统架构
  1. 进程调度器架构

    • 目标

      进程调度器是Linux内核中最重要的子系统。它的目的是控制计算机的CPU访问控制。这不仅仅包括用户进程的访问,也包括其他内核子系统的访问。

    • 模块

      进程调度器主要被分为四个部分:

      1. 调度策略模块负责判断哪个进程可以访问CPU,这个策略在设计上对于进程访问CPU是公平的。
      2. 架构相关模块通过设计成通用接口来抽取任何计算机架构的细节。这些模块负责和CPU通信来进行进程的挂起和恢复。这些操作包括了知道有哪些寄存器,每个进程需要保留的状态信息,然后执行代码来进行挂起和恢复操作。
      3. 架构无关模块和调度策略模块进行通信来决定下一个执行的进程,然后调用架构相关模块来恢复相应的进程。此外这个模块会调用内存管理器来确保恢复的进程的内存也被恢复。
      4. 系统调用接口模块允许用户进程访问被内核暴露出来的资源。这样就减少了用户进程对内核的依赖,只用依赖定义良好并且很少改变的接口,而不用管其他内核模块实现的变化。

      上下文中的进程调度器

    • 数据结构

      进程调度器维护着task list的数据结构,一个实体对应一个活跃的进程。这个数据结构包含着足够的信息来挂起和恢复进程,同时包含着额外的核对和状态信息。这个数据结构可以通过内核层得到。

    • 依赖,数据流,控制流

      就如前面提及,进程调度器调用了内存管理器子系统;因为这个原因,进程调度器子系统依赖内存管理器子系统。此外,当等待硬件请求完成时,其他内核子系统需要依赖进程调度器来挂起和恢复进程。这些依赖表现为功能调用和访问共享的task list数据结构。所有内核子系统读写代表当前任务的数据结构,形成了和进程调度器的双向数据流。

      除了在内核层内部的数据和控制流,O/S 服务层为用户进程提供了用于注册定时器的接口。这就形成了进程调度器对用户进程的控制流。在通常情况下恢复睡眠的进程不被认为形成了控制流因为用户进程无法检测这个操作。最后,进程调度器和CPU通信来挂起和恢复进程;这形成了数据流和控制流。CPU负责中断当前执行进程并允许内核调度另一个进程。

  2. 内存管理器架构

    • 目标

      内存管理器子系统负责控制进程访问内存硬件资源。通过硬件内存管理系统提供进程内存应用和机器物理内存的映射完成对内存资源访问的控制。内存管理子系统为每个进程维护映射,所以两个进程可以访问相同的虚拟内存地址而实际上使用的是不同的内存地址。此外内存管理器子系统支持换出;将不常使用的内存页移动到持久存储中来使得计算机支持虚拟内存比实际物理内存更大。

    • 模块

      内存管理器子系统有三个模块组成:

      1. 架构相关模块对内存管理硬件提出了虚拟接口。
      2. 架构无关管理器模块自行所有的进程的内存映射和虚拟内存的交换。这个模块负责决定当某个内存页发生错误时哪些内存页会被回收 – 这里没有独立的策略模块因为这个策略没有经常更改的需要。
      3. 系统调用接口为用户进程提供受限的访问。这个接口允许用户进程分配和释放内存,同时可以执行内存映射文件操作。
    • 数据表示

      内存管理器为每个进程存储着物理地址到虚拟地址的映射。这个映射在进程调度器的task list的数据结构中被存为一个引用。除了这个映射,在task list数据结构中还存储着额外的信息来告诉内存管理器如何取出和存储内存页。最后,内存管理器存储授权和账号信息来来确保系统安全。

      上下文中的内存管理器

    • 数据流,控制流,依赖

      内存管理器控制着内存硬件并在发生缺页错误时接收到通知 – 这就在内存管理器模块和内存管理器硬件间形成了双向的数据流和控制流。同样的,内存管理器使用文件系统来支持交换和内存映射I/O。这个要求意味着内存管理器需要对文件系统做程序调用来从持久存储中存储和取出内存页。因为文件系统请求并不能马上完成操作,所以内存管理器需要挂起进程直到内存被交换进来。这个请求导致了内存管理器需要对进程管理器做程序调用。同样,每个进程的内存映射被存储在进程调度器的数据结构中,所以在内存管理器和进程调度器间存在着双向数据流。用户进程可以在进程的地址空间内新建内存映射,可以在新映射的区域内注册内存页缺失错误的通知。这导致了从内存管理器通过系统调用接口模块到用户进程的控制流。在通常意义下没有从用户进程到内存管理器的数据流,但用户进程可以通过选择系统调用接口模块的系统调用来从内存管理器接收到一些信息。

  3. 虚拟文件系统架构

    • 目标

      虚拟文件系统设置的目的是为了对在硬件设备上存储的数据进行连续的表示。几乎所有的硬件设备都通过一个通用设备接口进行表示。虚拟文件更进一步,允许系统管理员将任何逻辑文件系统挂载到任何的物理设备上。逻辑文件系统提升和其他操作系统的兼容性,并允许开发者以不同的策略来实现文件系统。虚拟文件系统抽象了物理设备和逻辑文件系统的细节,使得用户进程使用统一接口来访问文件,而不需要知道文件存在于什么物理或逻辑系统。

      此外,除了通常的文件系统的目标,虚拟文件系统也负责加载可执行程序。这通过逻辑文件系统模块来完成,也使得Linux支持许多的可执行格式。

    • 模块

      1. 对于每个支持的硬件控制器都有一个设备驱动模块。因为有大量不兼容的硬件设备,就有着大量设备的驱动。而Linux的最通常的扩展就是支持新的设备驱动。
      2. 设备独立模块对所有的设备提供统一视图。
      3. 对于每个支持的文件系统都有一个逻辑文件系统。
      4. 系统无关接口对硬件资源提出了硬件和逻辑文件系统无关的视图。这个模块使用块或字节文件接口来表示所有资源。
      5. 最后,系统调用接口为用户进程访问文件系统提供受限的访问。虚拟文件系统只对用户进程暴露特定的功能。

      上下文中的虚拟文件系统

    • 数据表示

      所有文件使用i-nodes来进行表示。每个i-node结构包括位置信息来指定文件块载物理设备上的位置。此外,i-node存储指向逻辑文件系统中和设备驱动的用来执行必要的读写操作的程序。通过以这种方式存储功能指针,逻辑文件系统和设备驱动可以向内核注册而不用让内核依赖任何模块。

    • 数据流,控制流,依赖

      内存虚拟磁盘是一个特殊的设备驱动,这个设备分配主内存中的一块区域作为持久化存储设备。这个设备驱动通过内存管理器完成它的任务。因此,在文件系统设备驱动和内存管理器间有依赖,控制流和数据流。

      网络文件系统(只作为客户端)是一个被支持的特殊逻辑文件系统。这个文件系统访问其他机器的文件就好像这些文件是本地机器的一部分。为了实现这个,其中一个逻辑文件系统使用网络子系统来完成它的任务。这就在这两个子系统间造成了依赖,控制流,数据流。

      正如在3.2中所提及的,内存管理器使用虚拟文件系统来完成内存的交换和内存映射中的I/O。同时,虚拟文件系统使用进程调度器来挂起进程等待硬件操作的完成,恢复进程一旦操作完成。最后,系统调用接口允许用户进程调用虚拟文件系统来存储和检索数据。不像前面的子系统,这里没有为用户提供注册隐式调用的机制,所有从虚拟文件系统到用户进程并没有控制流。

  4. 网络接口模块

    • 目标

      网络子系统允许Linux系统通过网络和其他系统进行连接。有大量的硬件设备需要支持,大量的网络协议可以使用。网络子系统抽象了设备和协议的实现细节使得用户进程和其他的内核子系统可以访问网络而不需要知道什么物理设备和协议被使用。

    • 模块

      1. 网络设备驱动和硬件设备进行通信。对每一个硬件设备都有一个设备驱动模块
      2. 设备无法接口模块对所有的硬件设备提供了一致的视图,所以子系统中更高层次不需要知道正在使用的确切的硬件设备。
      3. 网络协议模块负责实现每一个可能的网络传输协议。
      4. 协议无关接口模块提供了与硬件设备和网络协议无法的接口。这是被其他内核子系统来访问网络而不需要依赖某个特定的协议或硬件的接口。
      5. 最后,系统调用接口限制用户进程可以访问的程序。

      上下文中的网络接口子系统

    • 数据表示

      每个网络对象被表示为一个socket。Sockets使用和i-nodes同样的方式和进程进行关联;进程间可以通过让task data数据结构指向相同的socket 数据结构来共享sockets。

    • 数据流,控制流,依赖

      网络子系统使用进程调度管理器来挂起和恢复进程当等待硬件完成操作。此外,网络子系统支持了虚拟文件系统通过实现了NFS的逻辑文件系统导致了虚拟文件系统依赖网络接口并形成了数据和控制流。

  5. 进程间通信架构

    进程间通信子系统架构因为不想其他子系统那样有趣而简化忽略了。


总结

Linux内核是整个Linux系统架构的一层。内核从概要上可以分为五个主要的子系统:进程调度器,内存管理器,虚拟文件系统,网络接口,进程间通信接口,这些子系统通过功能调用和共享数据结构来进行交互。

从最高的层次来说,Linux内核的架构风格与Garlan和Shaw的数据抽象风格非常接近。内核由子系统组成,而子系统维护内部表现的一致性通过提供特定的程序接口。正如每个子系统被阐述的一样,我们可以看到架构风格和层级风格十分相似。每个子系统由模块组成并只和相邻的层进行通信。

linux内核架构的概要已经证明了它的成功;这个成功的必要因素是开发者组织的准备,和系统可扩展性的准备。Linux内核架构要求支持大量独立志愿开发者。这个要求表明对于大部分的开发要求系统分离 – 硬件设备驱动和文件以及网络协议 – 要以一种可扩展的方式实现。Linux通过使用数据抽象技术来使系统架构接近可扩展:每个硬件设备驱动被作为一个支持相同接口的独立模块。以这种方式,一个独立的开发者可以加入性的设备驱动,并且和其他的Linux内核开发者有最小的交叉。被大量志愿开发者实现的Linux内核的成功证明了这个策略的正确性。

Linux内核另一个重要的扩展是支持更多的硬件平台。通过分离所有硬件相关代码到每个子系统的不同的模块,系统的架构实现了这样的扩展性。以这种方式,一个小团队的开发者可以对一个新的硬件架构实现Linux内核的端口,只要重新实现内核中机器相关的部分就行了。